#include "tools/my_tlv.h"
#include "crypt/crypt_7th.h"
#include "model/mysqlhelper.h"
#include "model/model_file.h"
#include <sys/sendfile.h>

// 将二进制哈希转为十六进制字符串
int bin2hex(unsigned char *bin, size_t len, char *hex) {
    for (size_t i = 0; i < len; i++) {
        sprintf(hex + 2*i, "%02x", bin[i]);
    }
    hex[2*len] = '\0';
    return 0;
}

// 比较哈希值
int compare_hash(unsigned char *hash1, const char *hex_hash2) {
    char hex_hash1[65];
    bin2hex(hash1, 32, hex_hash1);
    return strcmp(hex_hash1, hex_hash2) == 0;
}

/*
    函数功能：接收文件数据
    @param
    netfd: 与客户端连接的套接字描述符
    pstart: 缓冲区首地址的指针
    totalSize: 文件大小
*/
int recvn(int netfd, void* pstart, ssize_t totalSize)
{
    ssize_t finishSize = 0;
    ssize_t ret = 0;

    char* p = (char*)pstart;
    while (finishSize < totalSize)
    {
        ret = recv(netfd, p + finishSize, totalSize - finishSize, 0);
        if (ret == -1)
        {
            perror("ERROR: recv");
            return -1;
        }
        else if (ret == 0)
        {
            return 1;   // 客户端关闭传输，返回1
        }
        
        finishSize += ret;
    }

    return 0;
}

/*
    接收整个文件,  与transfer_file_by_send配合使用
    @param
    netfd: 套接字描述符
*/
int recv_file(int netfd)
{
    int ret;
    off_t filesize = 0, curr_size = 0, last_size = 0, slice = 0;
    train_t recv_train;
    struct stat statbuf;
    bzero(&recv_train, sizeof(recv_train));

    // 先接收文件名的长度
    recvn(netfd, &recv_train.length, sizeof(int));
    // 接收文件名
    recvn(netfd, recv_train.data, recv_train.length);

    printf("Recv file name: %s\n", recv_train.data);
    // 在当前目录创建一个同名文件
    int fd = open(recv_train.data, O_WRONLY | O_CREAT, 0666);
    if (fd == -1)
    {
        perror("Recver open");
        return -1;
    }

    // 再接收文件大小长度
    recvn(netfd, &recv_train.length, sizeof(int));
    // 接收文件大小
    recvn(netfd, recv_train.data, recv_train.length);

    off_t totalsize;
    memcpy(&totalsize, recv_train.data, recv_train.length);
    printf("recv total file size = %ld\n", totalsize);
    slice = totalsize / 10000;

    fstat(fd, &statbuf);
    filesize = statbuf.st_size;
    bzero(&recv_train, sizeof(recv_train));
    if (filesize > 0 && filesize < totalsize)
    {
        // 将文件操作符指针指向文件末尾
        off_t pos =  lseek(fd, 0, SEEK_END);
        if (pos == -1)
        {
            perror("ERROR calling lseek");
            close(fd);
            return -1;
        }
        // 发送已有文件的大小给对方
        recv_train.length = sizeof(filesize);
        memcpy(recv_train.data, &filesize, sizeof(off_t));
        ret = send(netfd, &recv_train, sizeof(int) + recv_train.length, MSG_NOSIGNAL);
        if (ret == -1)
        {
            perror("Receiver send file size");
            close(fd);
            return -1;
        }
    }
    else if (filesize == totalsize)
    {
        printf("The file is exist!\n");
        close(fd);
        return 0;
    }
    else
    {
        send(netfd, &recv_train, sizeof(int) + strlen(recv_train.data), MSG_NOSIGNAL);
    }

    while (1)
    {
        bzero(&recv_train, sizeof(recv_train));
        ret = recvn(netfd, &recv_train.length, sizeof(int));
        if (ret == -1)
        {
            close(fd);
            return -1;
        }

        if (recv_train.length == 0)
        {
            printf("Complete!\n");
            break;
        }
        curr_size += recv_train.length;
        if (curr_size - last_size > slice)
        {
            printf("%5.2lf%%\r", 100.0 * curr_size / totalsize);
            fflush(stdout);
            last_size = curr_size;
        }
        recvn(netfd, recv_train.data, recv_train.length);
        write(fd, recv_train.data, recv_train.length);
        if (recv_train.length != 1000)
        {
            printf("Last data lenght of train = %d\n", recv_train.length);
        }
    }
    close(fd);
    return 0;
}

/*
    接收整个文件，配合transfer_file_by_sendfile使用，适合大文件传输
    @param
    netfd: 套接字描述符
*/
int recv_from_sendfile(int netfd)
{
    int ret;
    off_t curr_size = 0, last_size = 0, slice = 0;
    train_t recv_train;
    struct stat statbuf;
    char buf[4096] = {0};
    char cmd[256] = {0};
    unsigned char peer_hash[32];
    unsigned char hash[32];
    char hex_hash[65];
    bzero(&recv_train, sizeof(recv_train));

    // 先接收命令
    recvn(netfd, &recv_train.length, sizeof(int));
    recvn(netfd, recv_train.data, recv_train.length);
    // 取出命令
    strncpy(cmd, recv_train.data, sizeof(cmd));

    // 接收对方传过来的文件哈希值
    ret = recv(netfd, peer_hash, sizeof(peer_hash), 0);
    if (ret == -1)
    {
        perror("Error: recv hash");
        return -1;
    }

    bzero(recv_train.data, strlen(recv_train.data));
    // 再接收文件长度
    recvn(netfd, &recv_train.length, sizeof(int));
    recvn(netfd, recv_train.data, recv_train.length);
    // 要传输的文件大小
    off_t totalsize;
    memcpy(&totalsize, recv_train.data, sizeof(off_t));
    printf("Recv total file size = %ld\n", totalsize);
    bzero(recv_train.data, strlen(recv_train.data));
    // 接收文件名
    recvn(netfd, &recv_train.length, sizeof(int));
    recvn(netfd, recv_train.data, recv_train.length);

    printf("Command: %s %s\n", cmd, recv_train.data);


    // 在当前目录创建一个同名文件
    int fd = open(recv_train.data, O_RDWR | O_CREAT, 0666);
    if (fd == -1)
    {
        perror("Recver open");
        return -1;
    }


    fstat(fd, &statbuf);
    curr_size = statbuf.st_size;
    bzero(&recv_train, sizeof(recv_train));
    // 判断接收方的文件实际大小是否小于要接受的文件，如果是，则向对方发送文件大小（也就是偏移量），开始断点传输
    if (curr_size >= 0 && curr_size < totalsize)
    {
        // 将文件操作符指针指向文件末尾
        off_t pos =  lseek(fd, 0, SEEK_END);
        if (pos == -1)
        {
            perror("Error: calling lseek");
            close(fd);
            return -1;
        }

        recv_train.length = sizeof(curr_size);
        memcpy(recv_train.data, &curr_size, sizeof(off_t));
        ret = send(netfd, &recv_train, sizeof(int) + recv_train.length, 0);
        if (ret == -1)
        {
            perror("Receiver send file size");
            close(fd);
            return -1;
        }
    }
    else if (curr_size == totalsize)
    {
        // 校验哈希值
        if (getFileSha256(fd, hash) != 0)
        {
            fprintf(stderr, "Error compute hash\n");
            close(fd);
            return -1;
        }
        // 把二进制的哈希值转换成十六进制的值
	    bin2hex(hash, 32, hex_hash);

		if (compare_hash(peer_hash, hex_hash))
		{
            // 如果命令是上传文件，则打印秒传成功
            if (strcmp(cmd, "puts") == 0)
            {
                printf("秒传成功！\n");
                FILE_M tsfile;
                MYSQL *conn = NULL;
                getConnect_7th(&conn);
                memcpy(tsfile.sha256_name, hex_hash, sizeof(tsfile.sha256_name));
                tsfile.file_size = totalsize;
                tsfile.ref_count = 1;
                tsfile.is_deleted = 0;
                if (selectFileBysha(conn, hex_hash, &tsfile) == 0)
                {
                    updateFileRef(conn, &tsfile);
                }

                // 关闭连接
                closeConnect_7th(conn);
            }
            else
            {
		        printf("Hash matches. The file is exist!\n");
            }
            // 发送长度为1的火车给对方，表示文件存在
            recv_train.length = 1;
            send(netfd, &recv_train, sizeof(int) + recv_train.length, 0);
            close(fd);
            return 0;
		}
		else
		{
		    printf("Hash mismatched! The file with the same name already exists\n");
		    printf("Expected: %s\n", hex_hash);
		    printf("Actual: ");
		    printHash(peer_hash, sizeof(peer_hash));

            send(netfd, &recv_train, sizeof(int) + recv_train.length, 0);
		    close(fd);
		    return 1;
		}
    }
    else
    {
        // 文件不存在，发送个空火车，告诉对方开始传输
        send(netfd, &recv_train, sizeof(int) + recv_train.length, 0);
    }

    ssize_t recv_bytes = 0;
    printf("Start transfer...\n");
    while (curr_size < totalsize)
    {
        bzero(buf, strlen(buf));
        if (curr_size - last_size > slice)
        {
            printf("%5.2lf%%\r", 100.0 * curr_size / totalsize);
            fflush(stdout);
            last_size = curr_size;
        }
        recv_bytes = recv(netfd, buf, sizeof(buf), 0);
        if (recv_bytes == 0)
        {
            printf("Connection closed.\n");
            break;
        }
        else if (recv_bytes < 0)
        {
            perror("recv");
            return -1;
        }

        if (write(fd, buf, recv_bytes) != recv_bytes)
        {
            perror("write");
            close(fd);
            return -1;
        }
        curr_size += recv_bytes;
    }

    if (curr_size == totalsize)
    {
        printf("100%% Done.\n");
        if (getFileSha256(fd, hash) != 0)
        {
            fprintf(stderr, "Error compute hash\n");
            close(fd);
            return -1;
        }
	    bin2hex(hash, 32, hex_hash);

		if (compare_hash(peer_hash, hex_hash))
		{
		    printf("Hash matches.\n");
            
            FILE_M tsfile;
            MYSQL *conn = NULL;
            getConnect_7th(&conn);
            memcpy(tsfile.sha256_name, hex_hash, sizeof(tsfile.sha256_name));
            tsfile.file_size = totalsize;
            tsfile.ref_count = 1;
            tsfile.is_deleted = 0;
            if (selectFileBysha(conn, hex_hash, &tsfile) == -1)
            {
                // 服务端接收文件时，插入文件信息到数据库
                int ret=0;
                insertFile(conn, &tsfile,&ret);
            }

            // 关闭连接
            closeConnect_7th(conn);
		}
		else
        {
		    printf("Hash mismatched!\n");
		    printf("Expected: %s\n", hex_hash);
		    printf("Actual: ");
		    printHash(peer_hash, sizeof(peer_hash));
        }
    }
    else
    {
        printf("Transmission error\n");
    }

    close(fd);
    return 0;
}

/*
    函数功能：send发送文件
    @param
    netfd: 与客户端连接的套接字描述符
    filename: 要发送的文件名称
*/
int transfer_file_by_send(int netfd, const char* filename)
{
    int isExist = access(filename, F_OK);
    if (isExist == -1)
    {
        fprintf(stderr, "ERROR: access %s", filename);
        return -1;
    }

    int ret = 0;
    int fd;
    train_t train;
    struct stat statbuf;
    bzero(&train, sizeof(train));
    train.length = strlen(filename);
    strncpy(train.data, filename, sizeof(train.data));

    // 先发送头部信息，长度是头部类型大小加文件名长度，数据区是文件名
    ret = send(netfd, &train, sizeof(train.length) + train.length, MSG_NOSIGNAL);
    if (ret == -1)
    {
        perror("ERROR: send train head");
        return -1;
    }

    fd = open(filename, O_RDONLY);
    if (fd == -1)
    {
        perror("ERROR: transfer_file open");
        return -1;
    }

    fstat(fd, &statbuf);
    printf("Transfer file \'%s\' size = %ld\n", filename, statbuf.st_size);
    // 记录文件大小
    off_t filesize = statbuf.st_size;
    train.length = sizeof(filesize);
    memcpy(train.data, &filesize, sizeof(off_t));
    // 将文件大小数据装进小火车发送给对方
    send(netfd, &train, sizeof(train.length) + train.length, MSG_NOSIGNAL);

    // 接收对方发过来的文件大小
    bzero(&train, sizeof(train));
    ret = recvn(netfd, &train.length, sizeof(int));
    if (ret == 1)
    {
        // 返回1，文件已存在无需传输
        printf("The file is exist, no need transmission.\n");
        return 0;
    }
    // 接收对方的同名文件长度
    ret = recvn(netfd, train.data, train.length);
    if (train.length > 0)
    {
        off_t cur_pos;
        memcpy(&cur_pos, train.data, train.length);
        lseek(fd, cur_pos, SEEK_SET);
    }

    while (1)
    {
        train.length = read(fd, train.data, sizeof(train.data));
        if (train.length == -1)
        {
            perror("ERROR: read file");
            close(fd);
            return -1;
        }


        if (train.length == 0)
        {
            bzero(&train, sizeof(train));
            send(netfd, &train, sizeof(train.length), MSG_NOSIGNAL);
            break;
        }

        ret = send(netfd, &train, sizeof(int) + train.length, MSG_NOSIGNAL);
        if (ret == -1)
        {
            perror("ERROR: send file data by train");
            close(fd);
            return -1;
        }
    }
    printf("Transmission completed!\n");
    close(fd);
    return 0;
}

/*
    函数功能：mmap传输文件
    @param
    netfd: 与客户端连接的套接字描述符
    filename: 要发送的文件名称
*/
int transfer_file_by_mmap(int netfd, const char* filename)
{
    int isExist = access(filename, F_OK);
    if (isExist == -1)
    {
        fprintf(stderr, "ERROR: access %s", filename);
        return -1;
    }

    int ret = 0;
    int fd = 0;
    train_t train;
    bzero(&train, sizeof(train));
    train.length = strlen(filename);
    strncpy(train.data, filename, sizeof(train.data));

    // 先发送头部信息，长度是头部类型大小加文件名长度
    ret = send(netfd, &train, sizeof(train.length) + train.length, MSG_NOSIGNAL);
    if (ret == -1)
    {
        perror("ERROR: send train head");
        return -1;
    }

    fd = open(filename, O_RDONLY);
    if (fd == -1)
    {
        perror("ERROR: transfer_file open");
        return -1;
    }

    struct stat statbuf;
    ret = fstat(fd, &statbuf);
    if (ret == -1)
    {
        perror("ERROR: fstat");
        return -1;
    }
    bzero(&train, sizeof(train));

    // 发送文件大小
    off_t filesize = statbuf.st_size;
    train.length = sizeof(filesize);
    memcpy(train.data, &filesize, train.length);
    send(netfd, &train, sizeof(int) + train.length, MSG_NOSIGNAL);

    // 接收对方发过来的文件大小
    bzero(&train, sizeof(train));
    ret = recvn(netfd, &train.length, sizeof(int));
    if (ret == 1)
    {
        // 返回1说明传输通道已经关闭，文件已存在无需传输
        printf("Client closed transmission.The file is exist.\n");
        close(fd);
        return 0;
    }

    // 接收对方的同名文件长度
    recvn(netfd, train.data, train.length);
    off_t cur_pos = 0;
    if (train.length > 0)
    {
        memcpy(&cur_pos, train.data, train.length);
    }

    printf("cur_pos = %ld\n", cur_pos);

    // 调用mmap进行内存映射
    char* p = (char*)mmap(NULL, filesize, PROT_READ, MAP_SHARED, fd, 0);
    if (p == MAP_FAILED)
    {
        perror("mmap");
        close(fd);
        return -1;
    }
    char* tmp = p + cur_pos;

    off_t total = cur_pos;
    while (total < statbuf.st_size)
    {
        if (statbuf.st_size - total > (long)sizeof(train.data))
        {
            train.length = sizeof(train.data);
        }
        else
        {
            train.length = statbuf.st_size - total;
        }
        memcpy(train.data, tmp + total, train.length);
        total += train.length;
        ret = send(netfd, &train, sizeof(int) + train.length, MSG_NOSIGNAL);
        if (ret == -1)
        {
            perror("send train data");
            munmap(p, filesize - cur_pos);
            close(fd);
            return -1;
        }
    }

    //发送结束信息
    train.length = 0;
    send(netfd, &train, sizeof(int), MSG_NOSIGNAL);
    printf("Transmission completed!\n");
    munmap(p, filesize - cur_pos);
    close(fd);
    return 0;
}

/*
    函数功能：sendfile发送文件
    @param
    netfd: 与客户端连接的套接字描述符
    filename: 要发送的文件名称
*/
int transfer_file_by_sendfile(int netfd, char* command, const char* filename)
{
    int ret = 0;
    int fd;
    off_t cur_pos = 0;
    ssize_t sent_bytes = 0;
    train_t train;
    bzero(&train, sizeof(train));

    // 发送命令
    train.length = strlen(command);
    strncpy(train.data, command, sizeof(train.data));
    ret = send(netfd, &train, sizeof(train.length) + train.length, 0);
    if (ret == -1)
    {
        perror("ERROR: send command");
        return -1;
    }

    printf("Command: %s %s\n", command, filename);

    int isExist = access(filename, F_OK);
    if (isExist == -1)
    {
        fprintf(stderr, "ERROR: file %s not exist\n", filename);
        close(netfd);
        return 0;
    }

    fd = open(filename, O_RDONLY);
    if (fd == -1)
    {
        perror("ERROR: transfer_file open");
        return -1;
    }

    unsigned char hash[32];
    if (getFileSha256(fd, hash) != 0)
    {
        fprintf(stderr, "Error compute hash\n");
        close(fd);
        return -1;
    }

    // 发送哈希值
    ret = send(netfd, &hash, sizeof(hash), 0);
    if (ret == -1)
    {
        perror("Error: send hash");
        return -1;
    }

    struct stat statbuf;
    fstat(fd, &statbuf);
    bzero(&train, sizeof(train));
    off_t filesize = statbuf.st_size;
    train.length = sizeof(filesize);
    memcpy(train.data, &filesize, train.length);
    // 发送文件大小
    send(netfd, &train, sizeof(int) + train.length, 0);

    // 发送文件名称
    train.length = strlen(filename);
    memcpy(train.data, filename, train.length);
    send(netfd, &train, sizeof(int) + train.length, 0);

    // 接收对方发过来的文件大小
    bzero(&train, sizeof(train));
    ret = recvn(netfd, &train.length, sizeof(int));
    if (ret == 1)
    {
        // 返回1, 传输中断
        printf("Transmission interrupt.\n");
        close(fd);
        return 0;
    }

    if (train.length == 1)
    {
        printf("The file is exist, no need transmission.\n");
        close(fd);
        return 0;
    }

    // 接收对方的同名文件长度
    recvn(netfd, train.data, train.length);
    if (train.length > 0)
    {
        memcpy(&cur_pos, train.data, train.length);
    }

    printf("cur_pos = %ld\n", cur_pos);

    while (cur_pos < filesize)
    {
        sent_bytes = sendfile(netfd, fd, &cur_pos, filesize - cur_pos);
        if (sent_bytes == -1)
        {
            perror("sendfile");
            break;
        }
    }

    if (cur_pos == filesize)
    {
        printf("OK!\n");
    }
    close(fd);
    return 0;
}
